package gzconfigcenter

import (
	"fmt"
	"time"

	"github.com/jinzhu/copier"
	"github.com/zeromicro/go-zero/core/conf"
	configurator "github.com/zeromicro/go-zero/core/configcenter"
	"github.com/zeromicro/go-zero/core/configcenter/subscriber"
)

type ConfigFileType string

const (
	ConfigFileTypeYaml ConfigFileType = "yaml"
	ConfigFileTypeJson ConfigFileType = "json"
	ConfigFileTypeToml ConfigFileType = "toml"
)

// EtcdConfig etcd配置
type EtcdConfig struct {
	Endpoints      []string
	Username       string
	Password       string
	ConfigKey      string
	ConfigFileType string
}

type ConfigManager[T any] interface {
	GetConfig() T
	GetPointerConfig() *T
	WatchConfig(listener ConfigUpdateListener[T])
}

type ConfigUpdateListener[T any] func(v T)

// ConfigManager 配置管理器
type ConfigManagerImpl[T any] struct {
	cc       configurator.Configurator[T]
	conf     *T
	listener ConfigUpdateListener[T]
}

// NewConfigManager 创建配置管理器
func NewConfigManager[T any](etcdConfig EtcdConfig, localConfigPath string) (ConfigManager[T], error) {
	// 如果etcd配置为空，则从本地文件加载
	if len(etcdConfig.Endpoints) == 0 {
		cm := &ConfigManagerImpl[T]{}
		if err := cm.loadLocalConfig(localConfigPath); err != nil {
			return nil, err
		}
		return cm, nil
	}

	// 创建 etcd subscriber
	ss := subscriber.MustNewEtcdSubscriber(subscriber.EtcdConf{
		Hosts: etcdConfig.Endpoints, // etcd 地址
		Key:   etcdConfig.ConfigKey, // 配置key
		User:  etcdConfig.Username,
		Pass:  etcdConfig.Password,
	})
	if etcdConfig.ConfigFileType == "" {
		etcdConfig.ConfigFileType = string(ConfigFileTypeYaml)
	}

	// 创建 configurator
	cc := configurator.MustNewConfigCenter[T](configurator.Config{
		Type: etcdConfig.ConfigFileType, // 配置值类型：json,yaml,toml
	}, ss)

	conf, err := cc.GetConfig()
	if err != nil {
		return nil, err
	}

	v := &ConfigManagerImpl[T]{
		cc:   cc,
		conf: &conf,
	}
	v.watchConfigWithListener()

	return v, nil
}

// loadConfig 加载配置
func (m *ConfigManagerImpl[T]) loadLocalConfig(localConfigPath string) error {
	var c T
	if err := conf.Load(localConfigPath, &c); err != nil {
		return fmt.Errorf("load local config error: %v", err)
	}
	m.conf = &c
	return nil
}

// GetConfig 获取当前配置 仅获取当前最新配置 不会随着配置变化而变化
func (m *ConfigManagerImpl[T]) GetConfig() T {
	return *m.conf
}

// GetPointerConfig 获取当前指针配置 会随着配置变化而变化
func (m *ConfigManagerImpl[T]) GetPointerConfig() *T {
	return m.conf
}

func (m *ConfigManagerImpl[T]) watchConfigWithListener() {
	// 如果想监听配置变化，可以添加 listener
	m.cc.AddListener(func() {
		// 休眠500ms 在获取配置时 可能会获取到上次配置 所以需要休眠一下
		time.Sleep(500 * time.Millisecond)
		v, err := m.cc.GetConfig()
		if err != nil {
			panic(err)
		}
		fmt.Printf("config changed: %#v\n", v)
		// 深拷贝
		copier.Copy(&m.conf, &v)

		// 如果配置有变化，则调用用户监听器
		if m.listener != nil {
			go func() {
				m.listener(v)
			}()
		}
	})

}

func (m *ConfigManagerImpl[T]) WatchConfig(listener ConfigUpdateListener[T]) {
	m.listener = listener
}
